#
# openlookup/client/multivalued.py
#
# Copyright 2008-2009 Helsinki Institute for Information Technology
# and the authors.
#
# Authors: Ken Rimey <rimey@hiit.fi>
#

# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import socket
from hashlib import sha1
from xmlrpclib import ServerProxy, Binary, Error

from openlookup.client.base import BaseProxy

status_messages = {0: 'successful',
                   1: 'over capacity',
                   2: 'try again'}

class OpenLookupError(Error):
    def __repr__(self):
        return "<OpenLookupError: %s>" % self.message

class Proxy(BaseProxy):
    def __init__(self, url=None, app='test', ttl=24*3600,
                 preresolve=True, verbose=False):
        BaseProxy.__init__(self, url, preresolve=preresolve)
        self.app = app
        self.ttl = ttl
        self.verbose = verbose  # xmlrpclib debugging flag

    def get(self, key, maxvals=10, placemark=''):
        values, placemark = self.invoke('get',
                                        Binary(key), int(maxvals),
                                        Binary(placemark), self.app)
        values = [v.data for v in values]
        placemark = placemark.data
        assert len(values) <= maxvals
        return values, placemark

    def get_details(self, key, maxvals=10, placemark=''):
        results, placemark = self.invoke('get_details',
                                         Binary(key), int(maxvals),
                                         Binary(placemark), self.app)
        results = [(value.data, ttl, hash_type, secret_hash.data)
                   for (value, ttl, hash_type, secret_hash) in results]
        placemark = placemark.data
        return results, placemark

    def put(self, key, value, secret='', ttl=None):
        ttl = ttl or self.ttl
        if not secret:
            status = self.invoke('put',
                                 Binary(key), Binary(value),
                                 ttl, self.app)
        else:
            secret_hash = sha1(secret).digest()
            status = self.invoke('put_removable',
                                 Binary(key), Binary(value),
                                 'SHA', Binary(secret_hash),
                                 ttl, self.app)

        if status:
            raise OpenLookupError(get_status_message(status))

    def rm(self, key, value, secret, ttl=None):
        ttl = ttl or self.ttl
        value_hash = sha1(value).digest()
        status = self.invoke('rm',
                             Binary(key), Binary(value_hash),
                             'SHA', Binary(secret), ttl, self.app)
        if status:
            raise OpenLookupError(get_status_message(status))

    def invoke(self, method, *args):
        tries = 5
        while True:
            url = self.url
            if not url:
                raise OpenLookupError('no gateway')

            proxy = ServerProxy(url, verbose=self.verbose)
            f = getattr(proxy, method)
            try:
                return f(*args)
            except (Exception, socket.error):
                self.reset()
                tries -= 1
                if tries <= 0:
                    raise

def get_status_message(result):
    return status_messages.get(result, 'unknown result code')
